﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace miniGameCollection
{
    public class SnakeGame : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        List<MinePosition> snakeNodes;
        MinePosition eggPos;

        string[,] displayArea;
        Random random = new Random(DateTime.Now.Millisecond);

        Texture2D square;
        Texture2D line;

        int[][] destination = { new int[] { 0,1 }, new int[] { 0,-1 },
                                new int[] { 1,0 }, new int[] { -1 , 0} };
        int[] curDes;

        int maxX;
        int maxY;
        int eggCount = 0;
        int speedUp = 0;

        int squareWidth = 40;
        int squareHeight = 40;

        SpriteFont gamefont;

        bool isGameOver;
        private double lastSpentTime;
        private int deltaTime = 650;
        private int minDeltaTime = 180;
        private int prevDelataTime;

        KeyboardState lastKeyboardstate;

        public SnakeGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            this.IsMouseVisible = true;

            StartGame();

            base.Initialize();
        }

        private void StartGame()
        {
            snakeNodes = new List<MinePosition>();
            displayArea = new string[18, 12];
            InitSnakePostion();
            lastSpentTime = 0;
            eggPos = GetRandomEgg();
            eggCount = 0;
            deltaTime = 600;
            speedUp = 0;

            isGameOver = false;
        }

        private MinePosition GetRandomEgg()
        {
            MinePosition egg = GetRandomPositiion();
            while (snakeNodes.Contains(egg))
            {
                egg = GetRandomPositiion();
            }
            return egg;
        }

        private void InitSnakePostion()
        {
            maxX = displayArea.GetLength(0) - 1;
            maxY = displayArea.GetLength(1) - 1;
            MinePosition snakeHead = GetRandomPositiion();
            while (!IsPositionAviable(snakeHead))
            {
                snakeHead = GetRandomPositiion();
            }
            snakeNodes.Add(snakeHead);
            int curIndex = random.Next(0,4);
            curDes = destination[curIndex];
            MinePosition snakeBody = new MinePosition(snakeHead.X - curDes[0],snakeHead.Y - curDes[1]);
            while (!IsPositionAviable(snakeBody))
            {
                curIndex = (++curIndex) % 4;
                curDes = destination[curIndex];
                snakeBody = new MinePosition(snakeHead.X - curDes[0], snakeHead.Y - curDes[1]);
            }
            snakeNodes.Add(snakeBody);
        }

        private bool IsPositionAviable(MinePosition pos)
        {
            int tmpX = pos.X;
            int tmpY = pos.Y;
            if (tmpX > -1 && tmpX <= maxX && tmpY > -1 && tmpY <= maxY)
            {
                return true;
            }
            return false;
        }
        private MinePosition GetRandomPositiion()
        {
            int x = random.Next(0,maxX + 1);
            int y = random.Next(0,maxY + 1);

            return new MinePosition(x,y);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            square = new Texture2D(GraphicsDevice, squareWidth, squareHeight);
            square.CreateBorder(1, Color.Black);

            line = new Texture2D(GraphicsDevice,2,480);
            line.CreateBorder(1, Color.Black);

            gamefont = Content.Load<SpriteFont>("gamefont");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// game-specific content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            bool isHighSpeed = false;

            // TODO: Add your update logic here
            KeyboardState keyboard = Keyboard.GetState();
            if (keyboard.IsKeyDown(Keys.Enter))
            {
                ReStartGame();
                isGameOver = false;
            }
            if (keyboard.IsKeyDown(Keys.Left))
            {
                if (curDes == destination[3])
                {
                    isHighSpeed = true;
                }
                else if (curDes != destination[2])
                {
                    curDes = destination[3]; 
                }
            }
            else if (keyboard.IsKeyDown(Keys.Right))
            {
                if (curDes == destination[2])
                {
                    isHighSpeed = true;
                }
                else if (curDes != destination[3])
                {
                    curDes = destination[2]; 
                }
            }
            else if (keyboard.IsKeyDown(Keys.Up))
            {
                if (curDes == destination[1])
                {
                    isHighSpeed = true;
                }
                else if (curDes != destination[0])
                {
                    curDes = destination[1]; 
                }
            }
            else if (keyboard.IsKeyDown(Keys.Down))
            {
                if (curDes == destination[0])
                {
                    isHighSpeed = true;
                }
                else if (curDes != destination[1])
                {
                    curDes = destination[0]; 
                }
            }
            if (isHighSpeed)
            {
                if (deltaTime > minDeltaTime)
                {
                    prevDelataTime = deltaTime;
                    deltaTime = minDeltaTime;
                }
            }
            else
            {
                if (prevDelataTime != default(int))
                {
                    deltaTime = prevDelataTime;
                }
            }

            if (CheckIsFailed())
            {
                isGameOver = true;
            }
            else
            {
                SnakeWalk(gameTime);
            }
            //EatEgg();
            lastKeyboardstate = keyboard;
            base.Update(gameTime);
        }

        private void EatEgg()
        {
            eggCount++;
            ++speedUp;
            if (speedUp > 5)
            {
                if (deltaTime >= 400)
                {
                    deltaTime = deltaTime - 80; 
                }
                else if (deltaTime >= minDeltaTime)
                {
                    deltaTime = deltaTime - 30;
                }
                speedUp = 0;
            }
            MinePosition head = new MinePosition(eggPos.X,eggPos.Y);
            snakeNodes.Insert(0,head);
        }

        private void SnakeWalk(GameTime gameTime)
        {
            lastSpentTime += gameTime.ElapsedGameTime.TotalMilliseconds;
            if (lastSpentTime > deltaTime)
            {
                MinePosition nextStep = new MinePosition(snakeNodes[0].X + curDes[0], snakeNodes[0].Y + curDes[1]);
                if (snakeNodes.Contains(nextStep))
                {
                    isGameOver = true;
                }
                else if (nextStep.X == eggPos.X &&
                    nextStep.Y == eggPos.Y)
                {
                    EatEgg();
                    eggPos = GetRandomEgg();
                }
                else
                {
                    for (int i = snakeNodes.Count - 1; i > 0; i--)
                    {
                        snakeNodes[i].X = snakeNodes[i - 1].X;
                        snakeNodes[i].Y = snakeNodes[i - 1].Y;
                    }
                    snakeNodes[0].X = snakeNodes[0].X + curDes[0];
                    snakeNodes[0].Y = snakeNodes[0].Y + curDes[1];
                }
                lastSpentTime = 0f;
            }
        }

        private void ReStartGame()
        {
            StartGame();
        }

        private bool CheckIsFailed()
        {
            return !IsPositionAviable(snakeNodes[0]);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin();
            
            spriteBatch.Draw(square, new Vector2(eggPos.X * squareWidth, eggPos.Y * squareHeight), Color.Red);
            foreach (MinePosition node in snakeNodes)
            {
                spriteBatch.Draw(square, new Vector2(node.X * squareWidth, node.Y * squareHeight), Color.Yellow);
            }

            string score = (eggCount * 10).ToString();
            Vector2 scoreSize = gamefont.MeasureString(score);
            spriteBatch.DrawString(gamefont, score, new Vector2(squareWidth * (maxX + 1), 50), Color.White);
            if (isGameOver)
            {
                string pressEnter = "Failed!Press Enter to restart";
                Vector2 pressEnterSize = gamefont.MeasureString(pressEnter);
                spriteBatch.DrawString(gamefont, pressEnter, new Vector2(graphics.GraphicsDevice.Viewport.Width / 2 - pressEnterSize.X / 2, graphics.GraphicsDevice.Viewport.Height - 200), Color.White);
            }

            spriteBatch.Draw(line, new Vector2(squareWidth * (maxX + 1), 0), Color.Black);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
